package cn.net.cfss.fgbp.provider.manage.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.KeyBoundCursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author wutao
 * @version 1.0
 * @date 2021/4/8 15:40
 */
@Aspect
@Component
@Slf4j
public class SynchronizedMethodAspect {


    @Resource
    private RedisTemplate<String, Object> redisTemplate;




    @Around(value = "@annotation(SynchronizedMethod)")
    public Object doBefore(ProceedingJoinPoint joinPoint) throws Exception {

        //获取注解中传入的值
        SynchronizedMethod synchronizedMethod = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(SynchronizedMethod.class);
        int limitSize = synchronizedMethod.limitSize();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        String name = method.getDeclaringClass().getName() + method.getName();

        //执行方法
        Method proceedMethod = joinPoint.getClass().getDeclaredMethod("proceed");

        String synchronizedkey = getSynchronizedkey(name);
        Object o = synchronizedMethod(limitSize, joinPoint, proceedMethod, synchronizedkey);
        return o;


    }


    /**
     * 单列执行方法
     * @param limitSize 最大等待次数
     * @param obj 需要执行方法的对象实例
     * @param method 需要执行的方法
     * @param key 分布式锁的key
     * @return 方法执行后的结果
     */
    private Object synchronizedMethod (Integer limitSize, Object obj, Method method, String key) throws Exception {
        String uuid = UUID.randomUUID().toString();
        if (limitSize == null) {
            //如果不提供默认值，默认50
            limitSize = 50;
        }

        int i = 1;

        try {
            Boolean lock = false;
            lock = redisTemplate.opsForValue().setIfAbsent(key, uuid, 1000L, TimeUnit.MILLISECONDS);
            //
            while (!lock) {
                //避免死循环了，必须能将while终止
                if (i > limitSize) {
                    log.error("获取锁失败，共尝试获取：{}", limitSize);
                    throw new Exception("获取分布式锁失败");
                }
                i++;
                //线程睡眠10毫秒后再尝试获取锁
                Thread.sleep(10L);
                lock = redisTemplate.opsForValue().setIfAbsent(key, uuid, 1000L, TimeUnit.MILLISECONDS);
            }

            //TODO 开启守护线程，在方法没有执行结束内，定时更新锁的失效时间

            //成功获取到锁，执行方法
            method.setAccessible(true);
            Object invoke = method.invoke(obj);
            return invoke;
        } finally {
            //必须释放锁
            Object o = redisTemplate.opsForValue().get(key);
            if (o != null) {
                //判断锁是不是自己创建的，如果是自己创建的,使用完则删除，否则不能删。
                if (uuid.equals((String)o)) {
                    redisTemplate.delete(key);
                }
            }

        }

    }

    private String getSynchronizedkey (String key) {
        return "synchronized_metohd_lock_" + key;
    }
}
